home *** CD-ROM | disk | FTP | other *** search
Java Source | 1996-02-01 | 21.2 KB | 891 lines |
- /*
- * Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
- *
- * Permission to use, copy, modify, and distribute this software
- * and its documentation for NON-COMMERCIAL purposes and without
- * fee is hereby granted provided that this copyright notice
- * appears in all copies. Please refer to the file "copyright.html"
- * for further important copyright and licensing information.
- *
- * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
- * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
- * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
- * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
- * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
- */
- /*
- * @(#)Animator.java 1.1 95/09/08 Herb Jellinek
- *
- * Copyright (c) 1995 Sun Microsystems, Inc. All Rights Reserved.
- *
- * Permission to use, copy, modify, and distribute this software
- * and its documentation for NON-COMMERCIAL purposes and without
- * fee is hereby granted provided that this copyright notice
- * appears in all copies. Please refer to the file "copyright.html"
- * for further important copyright and licensing information.
- *
- * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
- * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
- * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
- * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
- * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
- */
-
- import java.io.InputStream;
- import java.awt.*;
- import java.awt.image.ImageProducer;
- import java.applet.Applet;
- import java.applet.AudioClip;
- import java.util.Vector;
- import java.util.Hashtable;
- import java.util.Enumeration;
- import java.io.File;
- import java.net.URL;
- import java.net.MalformedURLException;
-
- /**
- * An applet that plays a sequence of images, as a loop or a one-shot.
- * Can have a soundtrack and/or sound effects tied to individual frames.
- *
- * @author Herb Jellinek
- * @version 1.1, 08 Sep 1995
- */
-
- public class Animator extends Applet implements Runnable {
-
- /**
- * The images, in display order (Images).
- */
- Vector images = null;
-
- /**
- * Duration of each image (Integers, in milliseconds).
- */
- Hashtable durations = null;
-
- /**
- * Sound effects for each image (AudioClips).
- */
- Hashtable sounds = null;
-
- /**
- * Position of each image (Points).
- */
- Hashtable positions = null;
-
- /**
- * Background image URL, if any.
- */
- URL backgroundImageURL = null;
-
- /**
- * Background image, if any.
- */
- Image backgroundImage = null;
-
- /**
- * Start-up image URL, if any.
- */
- URL startUpImageURL = null;
-
- /**
- * Start-up image, if any.
- */
- Image startUpImage = null;
-
- /**
- * The soundtrack's URL.
- */
- URL soundtrackURL = null;
-
- /**
- * The soundtrack.
- */
- AudioClip soundtrack;
-
- /**
- * Largest width.
- */
- int maxWidth = 0;
-
- /**
- * Largest height.
- */
- int maxHeight = 0;
-
- /**
- * Was there a problem loading the current image?
- */
- boolean imageLoadError = false;
-
- /**
- * The directory or URL from which the images are loaded
- */
- URL imageSource = null;
-
- /**
- * The directory or URL from which the sounds are loaded
- */
- URL soundSource = null;
-
- /**
- * The thread animating the images.
- */
- Thread engine = null;
-
- /**
- * The current loop slot - index into 'images.'
- */
- int frameNum;
-
- /**
- * frameNum as an Object - suitable for use as a Hashtable key.
- */
- Integer frameNumKey;
-
- /**
- * The current X position (for painting).
- */
- int xPos = 0;
-
- /**
- * The current Y position (for painting).
- */
- int yPos = 0;
-
- /**
- * The default number of milliseconds to wait between frames.
- */
- public static final int defaultPause = 3900;
-
- /**
- * The global delay between images, which can be overridden by
- * the PAUSE parameter.
- */
- int globalPause = defaultPause;
-
- /**
- * Whether or not the thread has been paused by the user.
- */
- boolean userPause = false;
-
- /**
- * Repeat the animation? If false, just play it once.
- */
- boolean repeat;
-
- /**
- * Load all images before starting display, or do it asynchronously?
- */
- boolean loadFirst;
-
- /**
- * The offscreen image, used in double buffering
- */
- Image offScrImage;
-
- /**
- * The offscreen graphics context, used in double buffering
- */
- Graphics offScrGC;
-
- /**
- * Can we paint yet?
- */
- boolean loaded = false;
-
- /**
- * Was there an initialization error?
- */
- boolean error = false;
-
- /**
- * What we call an image file in messages.
- */
- final static String imageLabel = "image";
-
- /**
- * What we call a sound file in messages.
- */
- final static String soundLabel = "sound";
-
- /**
- * Print silly debugging info?
- */
- boolean debug = false;
-
- /**
- * Info.
- */
- public String getAppletInfo() {
- return "Animator by Herb Jellinek";
- }
-
- /**
- * Parameter Info
- */
- public String[][] getParameterInfo() {
- String[][] info = {
- {"imagesource", "url", "a directory"},
- {"startup", "url", "displayed at startup"},
- {"background", "url", "displayed as background"},
- {"startimage", "int", "start index"},
- {"endimage", "int", "end index"},
- {"pause", "int", "milliseconds"},
- {"pauses", "ints", "milliseconds"},
- {"repeat", "boolean", "repeat or not"},
- {"positions", "coordinates", "path"},
- {"soundsource", "url", "audio directory"},
- {"soundtrack", "url", "background music"},
- {"sounds", "urls", "audio samples"},
- };
- return info;
- }
-
- /**
- * Print silly debugging info.
- */
- void dbg(String s) {
- if (debug) {
- System.out.println(s);
- }
- }
-
- final int setFrameNum(int newFrameNum) {
- frameNumKey = new Integer(frameNum = newFrameNum);
- return frameNum;
- }
-
- public synchronized boolean imageUpdate(Image img, int infoFlags,
- int x, int y,
- int width, int height) {
- if ((infoFlags & ERROR) != 0) {
- imageLoadError = true;
- }
-
- notifyAll();
- return true;
- }
-
- void updateMaxDims(Dimension dim) {
- maxWidth = Math.max(dim.width, maxWidth);
- maxHeight = Math.max(dim.height, maxHeight);
- }
-
- /**
- * Parse the IMAGES parameter. It looks like
- * 1|2|3|4|5, etc., where each number (item) names a source image.
- *
- * @return a Vector of (URL) image file names.
- */
- Vector parseImages(String attr)
- throws MalformedURLException {
- Vector result = new Vector(10);
- for (int i = 0; i < attr.length(); ) {
- int next = attr.indexOf('|', i);
- if (next == -1) next = attr.length();
- String file = attr.substring(i, next);
- result.addElement(new URL(imageSource, "T"+file+".gif"));
- i = next + 1;
- }
- return result;
- }
-
- /**
- * Fetch the images named in the argument, updating
- * maxWidth and maxHeight as we go.
- * Is restartable.
- *
- * @param images a Vector of URLs
- * @return URL of the first bogus file we hit, null if OK.
- */
- URL fetchImages(Vector images) {
- for (int i = 0; i < images.size(); i++) {
- Object o = images.elementAt(i);
- if (o instanceof URL) {
- URL url = (URL)o;
- tellLoadingMsg(url, imageLabel);
- Image im = getImage(url);
- try {
- updateMaxDims(getImageDimensions(im));
- } catch (Exception e) {
- return url;
- }
- images.setElementAt(im, i);
- }
- }
- return null;
- }
-
- /**
- * Parse the SOUNDS parameter. It looks like
- * train.au||hello.au||stop.au, etc., where each item refers to a
- * source image. Empty items mean that the corresponding image
- * has no associated sound.
- *
- * @return a Hashtable of SoundClips keyed to Integer frame numbers.
- */
- Hashtable parseSounds(String attr, Vector images)
- throws MalformedURLException {
- Hashtable result = new Hashtable();
-
- int imageNum = 0;
- int numImages = images.size();
- for (int i = 0; i < attr.length(); ) {
- if (imageNum >= numImages) break;
-
- int next = attr.indexOf('|', i);
- if (next == -1) next = attr.length();
-
- String sound = attr.substring(i, next);
- if (sound.length() != 0) {
- result.put(new Integer(imageNum),
- new URL(soundSource, sound));
- }
- i = next + 1;
- imageNum++;
- }
-
- return result;
- }
-
- /**
- * Fetch the sounds named in the argument.
- * Is restartable.
- *
- * @return URL of the first bogus file we hit, null if OK.
- */
- URL fetchSounds(Hashtable sounds) {
- for (Enumeration e = sounds.keys() ; e.hasMoreElements() ;) {
- Integer num = (Integer)e.nextElement();
- Object o = sounds.get(num);
- if (o instanceof URL) {
- URL file = (URL)o;
- tellLoadingMsg(file, soundLabel);
- try {
- sounds.put(num, getAudioClip(file));
- } catch (Exception ex) {
- return file;
- }
- }
- }
- return null;
- }
-
- /**
- * Parse the PAUSES parameter. It looks like
- * 1000|500|||750, etc., where each item corresponds to a
- * source image. Empty items mean that the corresponding image
- * has no special duration, and should use the global one.
- *
- * @return a Hashtable of Integer pauses keyed to Integer
- * frame numbers.
- */
- Hashtable parseDurations(String attr, Vector images) {
- Hashtable result = new Hashtable();
-
- int imageNum = 0;
- int numImages = images.size();
- for (int i = 0; i < attr.length(); ) {
- if (imageNum >= numImages) break;
-
- int next = attr.indexOf('|', i);
- if (next == -1) next = attr.length();
-
- if (i != next - 1) {
- int duration = Integer.parseInt(attr.substring(i, next));
- result.put(new Integer(imageNum), new Integer(duration));
- } else {
- result.put(new Integer(imageNum),
- new Integer(globalPause));
- }
- i = next + 1;
- imageNum++;
- }
-
- return result;
- }
-
- /**
- * Parse a String of form xxx@yyy and return a Point.
- */
- Point parsePoint(String s) throws ParseException {
- int atPos = s.indexOf('@');
- if (atPos == -1) throw new ParseException("Illegal position: "+s);
- return new Point(Integer.parseInt(s.substring(0, atPos)),
- Integer.parseInt(s.substring(atPos + 1)));
- }
-
-
- /**
- * Parse the POSITIONS parameter. It looks like
- * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
- * corresponding to a source image. Empty items mean that the
- * corresponding image has the same position as the preceding one.
- *
- * @return a Hashtable of Points keyed to Integer frame numbers.
- */
- Hashtable parsePositions(String param, Vector images)
- throws ParseException {
- Hashtable result = new Hashtable();
-
- int imageNum = 0;
- int numImages = images.size();
- for (int i = 0; i < param.length(); ) {
- if (imageNum >= numImages) break;
-
- int next = param.indexOf('|', i);
- if (next == -1) next = param.length();
-
- if (i != next) {
- result.put(new Integer(imageNum),
- parsePoint(param.substring(i, next)));
- }
- i = next + 1;
- imageNum++;
- }
-
- return result;
- }
-
- /**
- * Get the dimensions of an image.
- * @return the image's dimensions.
- */
- synchronized Dimension getImageDimensions(Image im)
- throws ImageNotFoundException {
- // Get the width of the image.
- int width;
- int height;
-
- while ((width = im.getWidth(this)) < 0) {
- try {
- wait();
- } catch (InterruptedException e) { }
- if (imageLoadError) {
- throw new ImageNotFoundException(im.getSource());
- }
- }
-
- // Get the height of the image.
- while ((height = im.getHeight(this)) < 0) {
- try {
- wait();
- } catch (InterruptedException e) { }
- if (imageLoadError) {
- throw new ImageNotFoundException(im.getSource());
- }
- }
-
- return new Dimension(width, height);
- }
-
- /**
- * Stuff a range of image names into a Vector.
- * @return a Vector of image URLs.
- */
- Vector prepareImageRange(int startImage, int endImage)
- throws MalformedURLException {
- Vector result = new Vector(Math.abs(endImage - startImage) + 1);
- if (startImage > endImage) {
- for (int i = startImage; i >= endImage; i--) {
- result.addElement(new URL(imageSource, "T"+i+".gif"));
- }
- } else {
- for (int i = startImage; i <= endImage; i++) {
- result.addElement(new URL(imageSource, "T"+i+".gif"));
- }
- }
- return result;
- }
-
-
- /**
- * Initialize the applet. Get parameters.
- */
- public void init() {
-
- try {
- String param = getParameter("IMAGESOURCE");
- imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
- dbg("IMAGESOURCE = "+param);
-
- param = getParameter("PAUSE");
- globalPause =
- (param != null) ? Integer.parseInt(param) : defaultPause;
- dbg("PAUSE = "+param);
-
- param = getParameter("REPEAT");
- repeat = (param == null) ? true : (param.equalsIgnoreCase("yes") ||
- param.equalsIgnoreCase("true"));
-
- int startImage = 1;
- int endImage = 1;
- param = getParameter("ENDIMAGE");
- dbg("ENDIMAGE = "+param);
- if (param != null) {
- endImage = Integer.parseInt(param);
- param = getParameter("STARTIMAGE");
- dbg("STARTIMAGE = "+param);
- if (param != null) {
- startImage = Integer.parseInt(param);
- }
- images = prepareImageRange(startImage, endImage);
- } else {
- param = getParameter("STARTIMAGE");
- dbg("STARTIMAGE = "+param);
- if (param != null) {
- startImage = Integer.parseInt(param);
- images = prepareImageRange(startImage, endImage);
- } else {
- param = getParameter("IMAGES");
- dbg("IMAGES = "+param);
- if (param == null) {
- showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
- "specified.");
- return;
- } else {
- images = parseImages(param);
- }
- }
- }
-
- param = getParameter("BACKGROUND");
- dbg("BACKGROUND = "+param);
- if (param != null) {
- backgroundImageURL = new URL(imageSource, param);
- }
-
- param = getParameter("STARTUP");
- dbg("STARTUP = "+param);
- if (param != null) {
- startUpImageURL = new URL(imageSource, param);
- }
-
- param = getParameter("SOUNDSOURCE");
- soundSource = (param == null) ? imageSource : new URL(getDocumentBase(), param + "/");
- dbg("SOUNDSOURCE = "+param);
-
- param = getParameter("SOUNDS");
- dbg("SOUNDS = "+param);
- if (param != null) {
- sounds = parseSounds(param, images);
- }
-
- param = getParameter("PAUSES");
- dbg("PAUSES = "+param);
- if (param != null) {
- durations = parseDurations(param, images);
- }
-
- param = getParameter("POSITIONS");
- dbg("POSITIONS = "+param);
- if (param != null) {
- positions = parsePositions(param, images);
- }
-
- param = getParameter("SOUNDTRACK");
- dbg("SOUNDTRACK = "+param);
- if (param != null) {
- soundtrackURL = new URL(soundSource, param);
- }
- } catch (MalformedURLException e) {
- showParseError(e);
- } catch (ParseException e) {
- showParseError(e);
- }
-
-
-
- setFrameNum(0);
- }
-
- void tellLoadingMsg(String file, String fileType) {
- showStatus("Animator: loading "+fileType+" "+abridge(file, 20));
- }
-
- void tellLoadingMsg(URL url, String fileType) {
- tellLoadingMsg(url.toExternalForm(), fileType);
- }
-
- void clearLoadingMessage() {
- showStatus("");
- }
-
- /**
- * Cut the string down to length=len, while still keeping it readable.
- */
- static String abridge(String s, int len) {
- String ellipsis = "...";
-
- if (len >= s.length()) {
- return s;
- }
-
- int trim = len - ellipsis.length();
- return s.substring(0, trim / 2)+ellipsis+
- s.substring(s.length() - trim / 2);
- }
-
- void loadError(URL badURL, String fileType) {
- String errorMsg = "Animator: Couldn't load "+fileType+" "+
- badURL.toExternalForm();
- showStatus(errorMsg);
- System.err.println(errorMsg);
- error = true;
- repaint();
- }
-
- void showParseError(Exception e) {
- String errorMsg = "Animator: Parse error: "+e;
- showStatus(errorMsg);
- System.err.println(errorMsg);
- error = true;
- repaint();
- }
-
- void startPlaying() {
- if (soundtrack != null) {
- soundtrack.loop();
- }
- }
-
- void stopPlaying() {
- if (soundtrack != null) {
- soundtrack.stop();
- }
- }
-
- /**
- * Run the animation. This method is called by class Thread.
- * @see java.lang.Thread
- */
- public void run() {
- Thread me = Thread.currentThread();
-
- me.setPriority(Thread.MIN_PRIORITY);
-
- if (! loaded) {
- try {
- // ... to do a bunch of loading.
- if (startUpImageURL != null) {
- tellLoadingMsg(startUpImageURL, imageLabel);
- startUpImage = getImage(startUpImageURL);
- try {
- updateMaxDims(getImageDimensions(startUpImage));
- } catch (Exception e) {
- loadError(startUpImageURL, "start-up image");
- }
- resize(maxWidth, maxHeight);
- repaint();
- }
-
- if (backgroundImageURL != null) {
- tellLoadingMsg(backgroundImageURL, imageLabel);
- backgroundImage = getImage(backgroundImageURL);
- repaint();
- try {
- updateMaxDims(
- getImageDimensions(backgroundImage));
- } catch (Exception e) {
- loadError(backgroundImageURL, "background image");
- }
- }
-
- URL badURL = fetchImages(images);
- if (badURL != null) {
- loadError(badURL, imageLabel);
- return;
- }
-
- if (soundtrackURL != null && soundtrack == null) {
- tellLoadingMsg(soundtrackURL, imageLabel);
- soundtrack = getAudioClip(soundtrackURL);
- if (soundtrack == null) {
- loadError(soundtrackURL, "soundtrack");
- return;
- }
- }
-
- if (sounds != null) {
- badURL = fetchSounds(sounds);
- if (badURL != null) {
- loadError(badURL, soundLabel);
- return;
- }
- }
-
- clearLoadingMessage();
-
- offScrImage = createImage(maxWidth, maxHeight);
- offScrGC = offScrImage.getGraphics();
- offScrGC.setColor(Color.lightGray);
-
- resize(maxWidth, maxHeight);
- loaded = true;
- error = false;
- } catch (Exception e) {
- error = true;
- e.printStackTrace();
- }
- }
-
- if (userPause) {
- return;
- }
-
- if (repeat || frameNum < images.size()) {
- startPlaying();
- }
-
- try {
- if (images.size() > 1) {
- while (maxWidth > 0 && maxHeight > 0 && engine == me) {
- if (frameNum >= images.size()) {
- if (!repeat) {
- return;
- }
- setFrameNum(0);
- }
- repaint();
-
- if (sounds != null) {
- AudioClip clip =
- (AudioClip)sounds.get(frameNumKey);
- if (clip != null) {
- clip.play();
- }
- }
-
- try {
- Integer pause = null;
- if (durations != null) {
- pause = (Integer)durations.get(frameNumKey);
- }
- if (pause == null) {
- Thread.sleep(globalPause);
- } else {
- Thread.sleep(pause.intValue());
- }
- } catch (InterruptedException e) {
- // Should we do anything?
- }
- setFrameNum(frameNum+1);
- }
- }
- } finally {
- stopPlaying();
- }
- }
-
- /**
- * Paint the current frame.
- */
- public void paint(Graphics g) {
- if (error || !loaded) {
- if (startUpImage != null) {
- g.drawImage(startUpImage, 0, 0, this);
- } else {
- if (backgroundImage != null) {
- g.drawImage(backgroundImage, 0, 0, this);
- } else {
- g.clearRect(0, 0, maxWidth, maxHeight);
- }
- }
- } else {
- if ((images != null) && (images.size() > 0)) {
- if (frameNum < images.size()) {
- if (backgroundImage == null) {
- offScrGC.fillRect(0, 0, maxWidth, maxHeight);
- } else {
- offScrGC.drawImage(backgroundImage, 0, 0, this);
- }
-
- Image image = (Image)images.elementAt(frameNum);
- Point pos = null;
- if (positions != null) {
- pos = (Point)positions.get(frameNumKey);
- }
- if (pos != null) {
- xPos = pos.x;
- yPos = pos.y;
- }
- offScrGC.drawImage(image, xPos, yPos, this);
- g.drawImage(offScrImage, 0, 0, this);
- } else {
- // no more animation, but need to draw something
- dbg("No more animation; drawing last image.");
- g.drawImage((Image)images.lastElement(), 0, 0, this);
- }
- }
- }
- }
-
- /**
- * Start the applet by forking an animation thread.
- */
- public void start() {
- if (engine == null) {
- engine = new Thread(this);
- engine.start();
- }
- }
-
- /**
- * Stop the insanity, um, applet.
- */
- public void stop() {
- if (engine != null && engine.isAlive()) {
- engine.stop();
- }
- engine = null;
- }
-
- /**
- * Pause the thread when the user clicks the mouse in the applet.
- * If the thread has stopped (as in a non-repeat performance),
- * restart it.
- */
- public boolean handleEvent(Event evt) {
- if (evt.id == Event.MOUSE_DOWN) {
- if (loaded) {
- if (engine != null && engine.isAlive()) {
- if (userPause) {
- engine.resume();
- startPlaying();
- } else {
- engine.suspend();
- stopPlaying();
- }
- userPause = !userPause;
- } else {
- userPause = false;
- setFrameNum(0);
- engine = new Thread(this);
- engine.start();
- }
- }
- return true;
- } else {
- return super.handleEvent(evt);
- }
- }
-
- }
-
-
- class ParseException extends Exception {
- ParseException(String s) {
- super(s);
- }
- }
-
- class ImageNotFoundException extends Exception {
- ImageNotFoundException(ImageProducer source) {
- super(source+"");
- }
- }
-
-